
// Copyright (c) 2010-2021 niXman (github dot nixman at pm dot me). All
// rights reserved.
//
// This file is part of YAS(https://github.com/niXman/yas) project.
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
//
//
// Boost Software License - Version 1.0 - August 17th, 2003
//
// Permission is hereby granted, free of charge, to any person or organization
// obtaining a copy of the software and accompanying documentation covered by
// this license (the "Software") to use, reproduce, display, distribute,
// execute, and transmit the Software, and to prepare derivative works of the
// Software, and to permit third-parties to whom the Software is furnished to
// do so, all subject to the following:
//
// The copyright notices in the Software and this entire statement, including
// the above license grant, this restriction and the following disclaimer,
// must be included in all copies of the Software, in whole or in part, and
// all derivative works of the Software, unless such copies or derivative
// works are solely in the form of machine-executable object code generated by
// a source language processor.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

#ifndef __yas__detail__io__header_hpp
#define __yas__detail__io__header_hpp

#include <yas/detail/config/config.hpp>

#include <yas/detail/io/io_exceptions.hpp>
#include <yas/detail/io/endian_conv.hpp>

#include <yas/detail/type_traits/type_traits.hpp>

#include <cstring>
#include <limits>

namespace yas {
    namespace detail {
        namespace header {

            /***************************************************************************/
            // 1字节对齐
#pragma pack(push, 1)
            union archive_header {
                struct {
                    // version 占用 std::uint8_t （即 1 个字节）中的 4 位，取值范围是 0 到 15。
                    std::uint8_t version : 4; // version      : 0...15

                    // type 占用 3 位，取值范围是 0 到 7，用于表示不同的归档类型，如二进制、文本、JSON 等。
                    std::uint8_t type : 3; // archive type : 0...7: binary, text, json

                    // endian 占用 1 位，用于表示字节序，0 表示小端序（LE），1 表示大端序（BE）。
                    std::uint8_t endian : 1; // endianness   : 0 - LE, 1 - BE

                    // compacted 占用 1 位，用于表示是否压缩，0 表示未压缩，1 表示压缩。
                    std::uint8_t compacted : 1; // compacted    : 0 - no, 1 - yes

                    // reserved 占用 7 位，保留未使用。
                    std::uint8_t reserved : 7; // reserved
                } bits;

                // 联合体中的另一个成员，占用 2 个字节。
                std::uint16_t u;
            };
#pragma pack(pop)

            static_assert(sizeof(archive_header) == sizeof(std::uint16_t), "alignment error");

            /**************************************************************************/

#ifdef YAS_SET_HEADER_BYTES
            static_assert(sizeof(YAS_PP_STRINGIZE(YAS_SET_HEADER_BYTES)) >= 4, "header bytes error");
            static constexpr std::uint8_t yas_id[] = {
                 YAS_PP_STRINGIZE(YAS_SET_HEADER_BYTES)[0]
                ,YAS_PP_STRINGIZE(YAS_SET_HEADER_BYTES)[1]
                ,YAS_PP_STRINGIZE(YAS_SET_HEADER_BYTES)[2]
            };
#else
            static constexpr std::uint8_t yas_id[] = { 'y', 'a', 's' };
#endif // YAS_SET_HEADER_BYTES

            // k_header_size为7字节，这里应该是有2个字节的备用的
            enum { k_header_size = sizeof(yas_id) + sizeof(std::uint16_t) * 2 };

            /**************************************************************************/

            // 读取头部信息
            template<typename IO>
            void read_header(IO& io, archive_header& h) {
                std::uint8_t buf[k_header_size];
                __YAS_THROW_READ_ERROR(k_header_size != io.read(buf, k_header_size));

                // 判断前三个字节是否为'y', 'a', 's'
                if (0 != std::memcmp(buf, yas_id, sizeof(yas_id))) {
                    __YAS_THROW_WRONG_ARCHIVE_HEADER();
                }

                constexpr std::uint8_t d = (std::numeric_limits<std::uint8_t>::max)();
                // 这是一个简化的ASCII码表，除'0'-'9'、'a'-'f'、'A'-'F'外，其他都用变量d代替
                static constexpr const std::uint8_t hexmap[256] = {
                     d , d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d
                    ,d , d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d
                    ,d , d, d, d, d, d, d, d, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, d, d
                    ,d , d, d, d, d,10,11,12,13,14,15, d, d, d, d, d, d, d, d, d
                    ,d , d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d,10,11,12
                    ,13,14,15, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d
                    ,d , d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d
                    ,d , d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d
                    ,d , d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d
                    ,d , d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d
                    ,d , d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d
                    ,d , d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d, d
                    ,d , d, d, d, d, d, d, d, d, d, d, d, d, d, d, d
                };

                // 如果buf[3] == '0', '0'对应的ASCII码为48，在hexmap中索引为48的正好是0
                // 如果buf[3] == 'F', 'F'对应的ASCII码为70，在hexmap中索引为70的正好是15
                const std::uint8_t p0 = hexmap[buf[3]];
                const std::uint8_t p1 = hexmap[buf[4]];
                const std::uint8_t p2 = hexmap[buf[5]];
                const std::uint8_t p3 = hexmap[buf[6]];

#if __YAS_LITTLE_ENDIAN
                // 4字符 转 2字节 的archive_header
                h.u = __YAS_SCAST(std::uint16_t, p0 * (1 << 12) + p1 * (1 << 8) + p2 * (1 << 4) + p3 * (1 << 0));
#else
                h.u = __YAS_SCAST(std::uint16_t, p0 * (1 << 0) + p1 * (1 << 4) + p2 * (1 << 8) + p3 * (1 << 12));
#endif
            }

            // 写入头部信息
            template<typename IO>
            void write_header(IO& io, const archive_header& h) {
                static const std::uint8_t hexchars[] = "0123456789ABCDEF";
                const std::uint8_t buf[k_header_size] = {
                    yas_id[0], yas_id[1], yas_id[2]
                    // 赞、赞、赞！！！
                    // archive_header在存储时，为什么还要做这样一个操作呢？
                    // 首先，archive_header本身要用来描述大小端字节序，h.u是一个std::uint16_t类型，它在存储时，也会涉及到大小端的问题
                    // 如果，写archive_header时不考虑字节序，那么，在读取时，就有可能得到不正确的字节序。

                    // 将2个字节的h.u编码为4个字符，h.u右移后与0x000F按位与，肯定是一个0-15的数值，因此，每4bit编码为"0123456789ABCDEF"其中的一个字符
            #if __YAS_LITTLE_ENDIAN
                    ,hexchars[(h.u >> 12) & 0x000F]
                    ,hexchars[(h.u >> 8) & 0x000F]
                    ,hexchars[(h.u >> 4) & 0x000F]
                    ,hexchars[(h.u) & 0x000F]
            #else
                    ,hexchars[(h.u) & 0x000F]
                    ,hexchars[(h.u >> 4) & 0x000F]
                    ,hexchars[(h.u >> 8) & 0x000F]
                    ,hexchars[(h.u >> 12) & 0x000F]
            #endif
                };

                __YAS_THROW_WRITE_ERROR(k_header_size != io.write(buf, k_header_size));
            }

            /**************************************************************************/

        } // ns header

        /***************************************************************************/

        template<std::size_t F>
        struct archive_version;

        // 以下为模版特化
        template<>
        struct archive_version<options::binary> {
            enum { value = yas::detail::binary_archive_version };
        };

        template<>
        struct archive_version<options::text> {
            enum { value = yas::detail::text_archive_version };
        };

        template<>
        struct archive_version<options::json> {
            enum { value = yas::detail::json_archive_version };
        };

        /**************************************************************************/

        template<std::size_t F>
        struct oarchive_header {
            template<typename IO>
            oarchive_header(IO& io) {
                // 没有no_header和json标识时，才会写archive_header
                __YAS_CONSTEXPR_IF(!(F & options::no_header) && !(F & yas::json)) {
                    // 确定archive type
                    constexpr std::uint8_t artype = __YAS_SCAST(std::uint8_t
                        , F & (options::binary | options::text | options::json)
                    );
                    // 确定大小端
                    constexpr std::uint8_t endian = __YAS_SCAST(std::uint8_t
                        , ((F & options::ehost) ? __YAS_BIG_ENDIAN : (F & options::ebig) ? 1 : 0)
                    );
                    // 是否进行压缩
                    constexpr bool compacted = __YAS_SCAST(bool, (F & yas::compacted));

                    const header::archive_header header = { {
                         __YAS_SCAST(std::uint8_t, version() & 15)
                        ,__YAS_SCAST(std::uint8_t, artype)
                        ,__YAS_SCAST(std::uint8_t, endian)
                        ,__YAS_SCAST(std::uint8_t, compacted)
                        ,__YAS_SCAST(std::uint8_t, 0u) // reserved
                    } };

                    header::write_header(io, header);
                }
            }

            // 使用json进行序列化时，不需要写archive header，因为json是文本文档，输出和加载json文件，按照json的规则来即可
            static constexpr std::size_t header_size() { return (F & yas::json) ? 0 : header::k_header_size; }
            static constexpr std::size_t flags() { return F; }

            // 获取archive type
            static constexpr options type() {
                return __YAS_SCAST(options, F & (options::binary | options::text | options::json));
            }

            // 是否为大端
            static constexpr bool is_big_endian() {
                // 当F中包含options::ehost时，主机决定是否为大端，否则，由F中是否包含options::ebig来决定
                return __YAS_SCAST(bool,
                    (F & options::ehost) ? __YAS_SCAST(bool, __YAS_BIG_ENDIAN) : __YAS_SCAST(bool, F & options::ebig)
                );
            }

            // 是否为小端
            static constexpr bool is_little_endian() {
                return __YAS_SCAST(bool,
                    (F & options::ehost) ? __YAS_SCAST(bool, __YAS_LITTLE_ENDIAN) : __YAS_SCAST(bool, F & options::elittle)
                );
            }

            // 主机字节序大小端
            static constexpr options host_endian() { return __YAS_BIG_ENDIAN ? options::ebig : options::elittle; }

            // 是否进行数据压缩
            static constexpr bool compacted() { return __YAS_SCAST(bool, (F & yas::compacted)); }

            // archive 版本binary、text、json有各自的版本号
            static constexpr std::size_t version() { return archive_version<type()>::value; }

            static constexpr bool is_readable() { return false; }
            static constexpr bool is_writable() { return true; }
        };

        /***************************************************************************/

#define __YAS_CHECK_IF_HEADER_INITED() \
    if ( !header.bits.version ) __YAS_THROW_ARCHIVE_NO_HEADER()

        template<std::size_t F>
        struct iarchive_header {
            template<typename IO>
            iarchive_header(IO& io)
                :header{}
            {
                // 没有no_header和json标识时，才会写archive_header
                // 也就是说，当使用binary和text等archive type，并且写archive header时，才会进行下面的处理
                __YAS_CONSTEXPR_IF(!(F & options::no_header) && !(F & yas::json)) {
                    // 读archive header
                    header::read_header(io, header);

                    // archive type的掩码
                    constexpr std::size_t mask = options::binary | options::text | options::json;
                    // 实际的archive type
                    constexpr std::uint8_t artype = __YAS_SCAST(std::uint8_t, F & mask);

                    // 下面三个判断，如果序列化数据时的archive type、archive version、compacted与反序列化时，不一致，将会抛异常
                    if (header.bits.type != artype) {
                        __YAS_THROW_BAD_ARCHIVE_TYPE()
                    }

                    if (header.bits.version != archive_version<F & mask>::value) {
                        __YAS_THROW_BAD_ARCHIVE_VERSION()
                    }

                    if (F & yas::compacted && !header.bits.compacted) {
                        __YAS_THROW_BAD_COMPACTED_MODE()
                    }
                }

                __YAS_CONSTEXPR_IF(F & yas::json) {
                    header.bits.version = json_archive_version;
                    header.bits.type = yas::json;
                }
            }

            // 使用json进行序列化时，不需要写archive header
            static constexpr std::size_t header_size() { return (F & yas::json) ? 0 : header::k_header_size; }
            static constexpr std::size_t flags() { return F; }

            // archive type
            options type() const {
                __YAS_CHECK_IF_HEADER_INITED()

                    return __YAS_SCAST(options, header.bits.type);
            }

            bool is_big_endian() const {
                __YAS_CHECK_IF_HEADER_INITED()

                    return header.bits.endian;
            }

            bool is_little_endian() const { return !is_big_endian(); }

            static constexpr options host_endian() {
                return __YAS_BIG_ENDIAN ? options::ebig : options::elittle;
            }

            bool compacted() const {
                __YAS_CHECK_IF_HEADER_INITED()

                    return header.bits.compacted;
            }

            std::size_t version() const {
                __YAS_CHECK_IF_HEADER_INITED()

                    return header.bits.version;
            }

            static constexpr bool is_readable() { return true; }
            static constexpr bool is_writable() { return false; }

        private:
            header::archive_header header;
        };

#undef __YAS_CHECK_IF_HEADER_INITED

        /***************************************************************************/

    } // namespace detail
} // namespace yas

#endif // __yas__detail__io__header_hpp
